package m3f.factorization;

import m3f.data.MultimodalDataProvider;
import m3f.data.MultimodalDataSet;
import m3f.matrix.Minibatch;

import org.jblas.DoubleMatrix;
import org.jblas.Solve;

public class OnlineNSE {
	
	protected MultimodalDataProvider trainingData;
	protected MultimodalDataProvider trainingData2;
	protected DoubleMatrix S;
	protected DoubleMatrix lambda;
	protected double error;
	protected int factors;
	protected double beta;
	
	public OnlineNSE(MultimodalDataProvider set){
		trainingData = set;
	}
	
	protected void computeError(){
		
		trainingData2.reset(false);
		System.out.println("Computing error");
		//long s = System.currentTimeMillis();
		ConcurrentErrorNSE ce = new ConcurrentErrorNSE(trainingData2, S, beta);
		Thread thread = new Thread(ce);
		thread.start();
		try{
			thread.join();
		}catch(InterruptedException ie){
			ie.printStackTrace();
		}
		this.error= ce.getError();
		
	}
	
	protected void initialize(int visualFeatures, int textFeatures){
		System.out.println("Initializing model with " + visualFeatures + " visual features and " +
				textFeatures + " text features.");
		S = DoubleMatrix.rand(visualFeatures, textFeatures).muli(0.5);
	}
	
	public DoubleMatrix projectVisualData(MultimodalDataSet test, double beta){
		System.out.println("Test set with " + test.matrixRows() + " samples with " + test.visualFeatures() + " visual features and " + test.textFeatures() + " text features");
		Minibatch V = test.maxVisualBatch();
		Minibatch T = test.maxTextBatch();
		DoubleMatrix A, B, U;
		A = S.transpose().mmul(S);
		B = A.add(DoubleMatrix.eye(T.matrix().rows).mmul(beta));
		U = Solve.solve(B, S.transpose().mmul(V.matrix()));
		return U;
	}
	
	public void run(double lambda, double gamma0, int epochs, int minibatch, boolean skipError){

		trainingData.setMinibatchSize(minibatch);
		initialize(trainingData.visualFeatures(), trainingData.textFeatures());

		if(!skipError){
			trainingData2 = trainingData.clone();
			computeError();
			System.out.println("Initial error: " + error);
		}
		int t = 0;
		DoubleMatrix A, C, D;
		long allStar = System.currentTimeMillis();
		long start;
		for(int i = 0; i < epochs; i++){
			trainingData.reset(true);
			System.out.println("Training epoch " + i);
			long acc = 0;
			while(trainingData.hasMoreData()){
				
				double gamma = gamma0/(1 + gamma0*lambda*t);
				
				// Prepare minibatch
				Minibatch vv = trainingData.nextVisual();
				Minibatch tt = trainingData.nextTextual();

				start = System.currentTimeMillis();
				vv.matrix().add(0.00001);
				tt.matrix().add(0.00001);
				
				// Compute the latent representation for the minibatch:
				A = vv.matrix().div(S.mmul(tt.matrix()).add(0.0001));
				C = S.add(A.sub(1).mmul(tt.matrix().transpose()).mul(gamma));
				//D = C.add(S.mul(gamma*lambda));
				S = C.mul(C.ge(0));
				t++;
				
				if(!skipError){
					if (t % 300 == 0){
						computeError();
						System.out.println("Coding error: " + error);
					}
				}
				acc += (System.currentTimeMillis() - start);
			}
			//System.out.println("Epoch " + i + ": " + (System.currentTimeMillis() - acc) + " ms");
		}
		System.out.println(" Total: " + (System.currentTimeMillis() - allStar) + " ms");
	}
	
	public DoubleMatrix getS(){
		return S;
	}

}